﻿using System;
using System.IO;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using Microsoft.WindowsAzure;

namespace EncryptDecrypt
{
    public class AzureTableCrypto
    {
        private static AzureTableCryptoKeyStore keyStore;

        public AzureTableCrypto(CloudStorageAccount acct)
        {
            this.Initialize(acct);
        }

        private void Initialize(CloudStorageAccount acct)
        {
            if (keyStore == null)
            {
                keyStore = new AzureTableCryptoKeyStore(acct);
            }
        }

        public byte[] EncryptString(int version, string s)
        {
            byte[] bytes = Encoding.Unicode.GetBytes(s);
            return this.Encrypt(version, bytes);
        }

        public string DecryptString(int version, byte[] cryptedBytes)
        {
            byte[] bytes = this.Decrypt(version, cryptedBytes);
            return Encoding.Unicode.GetString(bytes);
        }

        public byte[] Encrypt(int encryptionVersion, byte[] bytes)
        {
            using (MemoryStream msEncrypted = new MemoryStream())
            {
                using (var encryptor = keyStore.GetEncryptor(encryptionVersion))
                {
                    using (CryptoStream csEncrypt = new CryptoStream(msEncrypted, encryptor, CryptoStreamMode.Write))
                    {
                        using (MemoryStream inStream = new MemoryStream(bytes))
                        {
                            inStream.CopyTo(csEncrypt);
                        }
                        csEncrypt.Close();
                    }
                    return msEncrypted.ToArray();
                }
            }
        }

        public byte[] Decrypt(int encryptionVersion, byte[] bytes)
        {
            using (var decryptor = keyStore.GetDecryptor(encryptionVersion))
            {
                using (MemoryStream msDecrypted = new MemoryStream())
                {
                    using (CryptoStream csEncrypt = new CryptoStream(msDecrypted, decryptor, CryptoStreamMode.Write))
                    {
                        csEncrypt.Write(bytes, 0, bytes.Length);
                    }
                    return msDecrypted.ToArray();
                }
            }
        }

        /// <summary>
        /// Decrypt all properties on an object that are marked with the Encrypt attribute
        /// </summary>
        public void DecryptObject(int encryptionVersion, object e)
        {
            foreach (PropertyInfo property in e.GetType().GetProperties())
            {
                object[] transparentEncryptAttributes = property.GetCustomAttributes(typeof(EncryptAttribute), false);
                if (transparentEncryptAttributes.Length > 0 && property.PropertyType == typeof(string))
                {
                    string propertyValue = (string)property.GetValue(e, null);
                    if (!string.IsNullOrEmpty(propertyValue))
                    {
                        byte[] cryptedBytes = Convert.FromBase64String(propertyValue);
                        propertyValue = this.DecryptString(encryptionVersion, cryptedBytes);
                        property.SetValue(e, propertyValue, null);
                    }
                }
            }
        }

        /// <summary>
        /// Encrypt all properties on an object that are marked with the Encrypt attribute
        /// </summary>
        /// <param name="encryptionVersion"></param>
        /// <param name="e"></param>
        public void EncryptObject(int encryptionVersion, object e)
        {
            foreach (PropertyInfo property in e.GetType().GetProperties())
            {
                object[] transparentEncryptAttributes = property.GetCustomAttributes(typeof(EncryptAttribute), false);
                if (transparentEncryptAttributes.Length > 0 && property.PropertyType == typeof(string))
                {
                    string propertyValue = (string)property.GetValue(e, null);
                    if (!string.IsNullOrEmpty(propertyValue))
                    {
                        byte[] cryptedBytes = this.EncryptString(encryptionVersion, propertyValue);
                        string cryptedString = Convert.ToBase64String(cryptedBytes);
                        property.SetValue(e, cryptedString, null);
                    }
                }
            }
        }
    }
}